module Main where


import Dict
import Lenguaje
import Parser
import ByValue
import ByName
import HUnit
import Debug.Trace

-------------------------
-- Tests Dict
-------------------------

diccionario = makeDict [
    ("hola","fonola"),
    ("fernet", "con coca"),
    ("plp", "ojala sea promocionable")
  ]
  
dictVacio :: Dict Int Int
dictVacio = emptyDict

testsDict = test [
    "testDict1" ~: "(lookupDict dictVacio 3)"
        ~: Nothing  ~=? (lookupDict dictVacio 3),
    "testDict2" ~: "(lookupDict diccionario hola)" 
        ~: Just "fonola" ~=? (lookupDict diccionario "hola"),
    "testDict3" ~: "(lookupDict diccionario fernet)" 
        ~: Just "con coca" ~=? (lookupDict diccionario "fernet"),
    "testDict4" ~: "(lookupDict (removeDict diccionario hola) hola))" 
        ~: Nothing ~=? (lookupDict (removeDict diccionario "hola") "hola"),
    "testDict5" ~: "(lookupDict (extendDict diccionario hola vecinillo) hola)"
        ~: Just "vecinillo" ~=? (lookupDict (extendDict diccionario "hola" "vecinillo") "hola"),
    "testDict6" ~: "(lookupDict diccionario tito)"
        ~: Nothing ~=? (lookupDict diccionario "tito"),
    "testDict7" ~: "(lookupDict (extendDict diccionario tito perez) tito)"
        ~: Just "perez" ~=? (lookupDict (extendDict diccionario "tito" "perez") "tito"),
    "testDict8" ~: "(lookupDict (extendDict (removeDict diccionario hola) hola como va) hola)"
        ~: Just "como va" ~=? (lookupDict (extendDict (removeDict diccionario "hola") "hola" "como va") "hola")
  ]
        
-------------------------------
-- Test FoldExp
-------------------------------

-- Funcion que usa foldExp, la podemos usar para probar su funcionamiento

-- Chequea que no halla variables libres (asume que las definiciones de las funciones no las tienen)
sinLibres exp = (sinLibres' exp) []

sinLibres' :: Exp -> [VarId] -> Bool
sinLibres' exp = foldExp (\id exps -> (\l -> foldr (\x y -> (x l) && y) True exps))
                         (\var exp1 exp2 -> (\l -> (exp1 l) && (exp2 (var:l))))
						 (\cond exp1 exp2 -> (\l -> (cond l) && (exp1 l) && (exp2 l)))
						 (\op exp1 exp2 -> (\l -> (exp1 l) && (exp2 l)))
						 (\var ->  (\l -> if var `elem` l then True else False))
						 (\val -> (\l -> True))
						 exp
						 
testsFoldExp= test [ 
              "testFoldExp1" ~: "sinLibres (Let \"x\" (Const 3) (Const 4))" ~: True ~=? sinLibres (Let "x" (Const 3) (Const 4)),
			  "testFoldExp2" ~: "sinLibres (Let \"x\" (Const 3) (Var \"x\"))" ~: True ~=? sinLibres (Let "x" (Const 3) (Var "x")),
              "testFoldExp3" ~: "sinLibres (Let \"x\" (Call \"hola\" [Var \"y\"]) (Const 3))" ~: False ~=? sinLibres (Let "x" (Call "hola" [Var "y"]) (Const 3)),
			  "testFoldExp4" ~: "sinLibres (Call \"chau\" [(BinOp Mul (Const 4) (Const 7)), (Const 3), \
			                    \(IfZero (Const 4) (Let \"y\" (Const 8) (BinOp Sub (Var \"y\") (Const 4))) \
								\(Const 10))])" ~: True ~=? 
								sinLibres (Call "chau" [(BinOp Mul (Const 4) (Const 7)), (Const 3), 
								(IfZero (Const 4) (Let "y" (Const 8) (BinOp Sub (Var "y") (Const 4))) (Const 10))])
					]

--------------------------
-- Test substitute 
--------------------------
-- Ejecutar y observar el resultado 
sustitucion1 = (makeDict [("x",Const 8),("y", Var "x"), ("z", Call "hola" [Const 2])])
testSubstitute1 = mostrar (substitute sustitucion1 (Let "x" (Var "z") (Var "y"))) --Let x=z in Y -> Let x=hola(2) in x
testSubstitute2 = mostrar  (substitute sustitucion1 (Let "x" (Var "x") (Var "x"))) --Let x = x in x -> Let x=8 in x"
testSubstitute3 = (mostrar (substitute sustitucion1 (Call "chau" [Var "z", Var "y", Var "x", 
        IfZero (Var "z") (Const 4) (Let "z" (Var "x") (BinOp Mul (Var "z") (Const 3)))])))
-- chau(z, y, x, ifZero(z) then 4 else let z=x in (z * 3)) ->  chau(hola(2), x, 8, ifZero(hola(2)) then 4 else let z=8 in (z * 3))


-----------------------
--Test reduceOneStep
----------------------- 

testOneStep1 = mostrar (fromResult (reduceOneStep emptyDict (Let "x" (BinOp Mul (Const 8) (Const 7)) (BinOp Sub (Const 40) (Var "x")))))
testOneStep2 = mostrar (fromResult (reduceOneStep (makeDict [("hola", FuncDef ["x"] (IfZero (BinOp Sub (Const 40) (Var "x")) (Const 0) (Const 1)))])  (Call "hola" [(Let "x" (Var "z") (Var "x"))])))
testOneStep3 = mostrar (fromResult (reduceOneStep emptyDict (Let "x" (Call "hola" [IfZero (Const 3) (Var "g") (Var "t")]) (Call "chau" [Var "x"]))))

					
----------------------------------------------------------------
-- Tests de funciones en el lenguaje y su evaluación/reducción
----------------------------------------------------------------


-- Definiciones de funciones en el lenguaje para testear su ejecución

def :: String -> String -> (FuncId, FuncDef)
def proto body = d (parser proto) (parser body)
  where d (Call f args) body = (f, FuncDef (map unVar args) body)
        unVar (Var x) = x



definiciones = makeDict [
  def "sumartodos(n)"
    "ifZero n then n \
    \ else n + sumarTodos(n - 1)" ,
					
  def "cruzada(n)"
    "ifZero n then 0 \
    \ else cruzadan(n-1)",
						  
  def "cruzadan(n)"
    "ifZero n then 1 \
    \ else (Let x=cruzada(n-1) in x)",
						 
  def "colgarse(x)"
    "colgarse(x-1)",
						
  def "andear(b,c)"
    "ifZero b then 0 \
    \ else ifZero c then 0 \
    \ else 1",
  def "menor(a,b)"
    "ifZero a then (ifZero b then 1 else 0) else ifZero b then 1 else menor(a-1,b-1)",

  def "cociente(a,b)"
    "ifZero b then colgarse(b) \
    \ else ifZero menor(a,b) then 0 \
    \ else let x=cociente(a - b, b) in (1 +x) ",

  def "resto(a,b)"
    "ifZero b then colgarse(b) \
    \ else ifZero menor(a,b) then a \
    \ else let y=resto(a-b,b) in y ",
                  
  def "ackermann(m,n)"
    "ifZero m then n+1 \
    \ else ifZero n then ackermann(m-1,1) \
    \ else ackermann(m-1, ackermann(m,n-1))",

  def "f(y)"
    "ifZero y then 0 else let x= y in f(x-1) + y",

  def "ceroOY(x,y)"
    "ifZero x then x else y",

  def "factorial(k)"
    "ifZero k then 1 else let k=k*factorial(k-1) in k",

  def "ff(k)"
    "let x=colgarse(k) in k"
  ]
                       
                   
main1  = parser "sumarTodos(sumarTodos(4))"
main2  = parser "Let x=5 in sumarTodos(sumarTodos(x))"
main3  = parser "cruzada(10)"
main4  = parser "cruzada(11)"
main5  = parser "andear(0,colgarse(2))"
main6  = parser "let j=5 in (let j=2 in j)+j"
main7  = parser "cociente(10,2)"
main8  = parser "ackermann(2,2)"
main9  = parser "resto(17,5)"
main10 = parser "f(f(4))"
main11 = parser "ceroOY(0,menor((2-3),(2-3)))"
main12 = parser "factorial(5)"
main13 = parser "ff(3)"
main14 = parser "cociente(2,1)"


-------------------------
-- Tests ByValue (eval)
-------------------------

testsByValue = test [ 
    "test1"  ~: "(eval definiciones main1)"  ~: 55  ~=? (eval definiciones main1),
    "test2"  ~: "(eval definiciones main2)"  ~: 120 ~=? (eval definiciones main2),
    "test3"  ~: "(eval definiciones main3)"  ~: 0   ~=? (eval definiciones main3),
    "test4"  ~: "(eval definiciones main4)"  ~: 1   ~=? (eval definiciones main4),
    "test6"  ~: "(eval definiciones main6)"  ~: 7   ~=? (eval definiciones main6),
    "test7"  ~: "(eval definiciones main7)"  ~: 5   ~=? (eval definiciones main7),
    "test8"  ~: "(eval definiciones main8)"  ~: 7   ~=? (eval definiciones main8),
    "test9"  ~: "(eval definiciones main9)"  ~: 2   ~=? (eval definiciones main9),
    "test10" ~: "(eval definiciones main10)" ~: 55  ~=? (eval definiciones main10),
    "test12" ~: "(eval definiciones main12)" ~: 120 ~=? (eval definiciones main12)
  ]


-- Tests que deben tildarse evaluando por valor
test5  = (eval definiciones main5)
test11 = (eval definiciones main11)
test13 = (eval definiciones main13)


-------------------------
-- Tests ByName (reduce)
-------------------------

testsByName = test [ 
    "test1"  ~: "(reduce definiciones main1)"  ~: 55  ~=? (reduce definiciones main1),
    "test2"  ~: "(reduce definiciones main2)"  ~: 120 ~=? (reduce definiciones main2),
    "test3"  ~: "(reduce definiciones main3)"  ~: 0   ~=? (reduce definiciones main3),
    "test4"  ~: "(reduce definiciones main4)"  ~: 1   ~=? (reduce definiciones main4),
    "test5"  ~: "(reduce definiciones main5)"  ~: 0   ~=? (reduce definiciones main5),  
    "test6"  ~: "(reduce definiciones main6)"  ~: 7   ~=? (reduce definiciones main6),
    "test7"  ~: "(reduce definiciones main7)"  ~: 5   ~=? (reduce definiciones main7),
    "test8"  ~: "(reduce definiciones main8)"  ~: 7   ~=? (reduce definiciones main8),
    "test9"  ~: "(reduce definiciones main9)"  ~: 2   ~=? (reduce definiciones main9),
    "test10" ~: "(reduce definiciones main10)" ~: 55  ~=? (reduce definiciones main10),
    "test11" ~: "(reduce definiciones main11)" ~: 0   ~=? (reduce definiciones main11),
    "test12" ~: "(reduce definiciones main12)" ~: 120 ~=? (reduce definiciones main12),
    "test13" ~: "(reduce definiciones main13)" ~: 3   ~=? (reduce definiciones main13)
  ]


main = do runTestTT testsDict
          runTestTT testsByValue
          runTestTT testsByName

